package pe.bf.base.dao.impl;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;

import javax.sql.DataSource;

import pe.bf.base.dao.BaseDAO;
import pe.bf.domain.exceptions.DomainException;

public class BaseDAOImpl implements BaseDAO {

	protected DataSource dataSource;

	/**
	 * 
	 * @param dataSource
	 */
	public BaseDAOImpl(DataSource dataSource) {
		super();
		this.dataSource = dataSource;
	}

	/**
	 * Retorna una conexion
	 * 
	 * @return
	 * @throws SQLException
	 */
	public Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}

	/**
	 * Invoca un store procedure y retorna un resultset
	 * 
	 * @param storeProcedureName
	 * @return
	 * @throws DomainException
	 */
	public boolean ivokeStoreProcedure(String storeProcedureName,
			Object... parameters) throws DomainException {

		CallableStatement callableStatement = null;
		boolean result = false;
		Connection connection = null;
		try {
			connection = getConnection();
			callableStatement = connection.prepareCall(storeProcedureName);
			fillStatement(callableStatement, parameters);
			result = callableStatement.execute();
		} catch (SQLException e) {
			throw new DomainException(e);
		} finally {
			close(connection);
			close(callableStatement);
		}

		return result;

	}

	/**
	 * Invoca un store procedure y retorna un resultset
	 * 
	 * @param storeProcedureName
	 * @return
	 * @throws DomainException
	 */
	public ResultSet ivokeStoreProcedure(String storeProcedureName,
			String objectName, Object... parameters) throws DomainException {

		CallableStatement callableStatement = null;
		ResultSet resultSet = null;
		Connection connection = null;
		try {
			connection = getConnection();
			callableStatement = connection.prepareCall(storeProcedureName);
			fillStatement(callableStatement, parameters);
//			callableStatement.registerOutParameter(objectName, OracleTypes.CURSOR);
			callableStatement.execute();
			resultSet = (ResultSet) callableStatement.getObject(objectName);
		} catch (SQLException e) {
			throw new DomainException(e);
		} finally {
			close(connection);
			close(callableStatement);
		}

		return resultSet;

	}

	/**
	 * Fill the <code>CallableStatement</code> replacement parameters with the
	 * given objects.
	 * 
	 * @param stmt
	 *            PreparedStatement to fill
	 * @param params
	 *            Query replacement parameters; <code>null</code> is a valid
	 *            value to pass in.
	 * @throws SQLException
	 *             if a database access error occurs
	 */
	public void fillStatement(CallableStatement stmt, Object... params)
			throws SQLException {

		// check the parameter count, if we can
		ParameterMetaData pmd = null;
		// if (!pmdKnownBroken) {
		pmd = stmt.getParameterMetaData();
		int stmtCount = pmd.getParameterCount();
		int paramsCount = params == null ? 0 : params.length;

		if (stmtCount != paramsCount) {
			throw new SQLException("Wrong number of parameters: expected "
					+ stmtCount + ", was given " + paramsCount);
		}
		// }

		// nothing to do here
		if (params == null) {
			return;
		}

		for (int i = 0; i < params.length; i++) {
			if (params[i] != null) {
				stmt.setObject(i + 1, params[i]);
			} else {
				// VARCHAR works with many drivers regardless
				// of the actual column type. Oddly, NULL and
				// OTHER don't work with Oracle's drivers.
				int sqlType = Types.VARCHAR;
				// if (!pmdKnownBroken) {
				try {
					/*
					 * It's not possible for pmdKnownBroken to change from true
					 * to false, (once true, always true) so pmd cannot be null
					 * here.
					 */
					sqlType = pmd.getParameterType(i + 1);
				} catch (SQLException e) {
					// pmdKnownBroken = true;
				}
				// }
				stmt.setNull(i + 1, sqlType);
			}
		}
	}

	/**
	 * cierra un <code>Connection</code>, evita el cierre si es nulo.
	 * 
	 * @param connection
	 * @throws SQLException
	 */
	public static void close(Connection connection) throws DomainException {
		if (connection != null) {
			try {
				connection.close();
			} catch (SQLException e) {
				throw new DomainException(e);
			}
		}
	}

	/**
	 * cierra un <code>ResultSet</code>, evita el cierre si es nulo
	 * 
	 * @param resultSet
	 * @throws SQLException
	 */
	public static void close(ResultSet resultSet) throws DomainException {
		if (resultSet != null) {
			try {
				resultSet.close();
			} catch (SQLException e) {
				throw new DomainException(e);
			}
		}
	}

	/**
	 * cierra un <code>Statement</code>, evita el cierre si es nulo.
	 * 
	 * @param stmt
	 * @throws SQLException
	 */
	public static void close(Statement statement) throws DomainException {
		if (statement != null) {
			try {
				statement.close();
			} catch (SQLException e) {
				throw new DomainException(e);
			}
		}
	}

}
